Engineering case study · Privacy-first FinTech

FinanceTracker

A self-hosted personal finance tracker that turns monthly CommBank and Westpac CSV exports into an AI-categorised spending dashboard, on desktop and as an installable phone PWA, where privacy is a property of the architecture, not a promise.

100% local data · nothing sensitive ever leaves the machine
01 / BY THE NUMBERS
Automated tests
2,106
zero live network calls
Shipped versions
7
v1 → v7
Banks parsed
2
CommBank · Westpac
Local data
100%
your keys, your hardware
02 / PRIVACY ARCHITECTURE

One brain, two windows

FastAPI · single source of truth
parse dedupe sanitise categorise store excel drive

One FastAPI backend owns all data and logic; the desktop dashboard and phone PWA are stateless views of it, served privately over Tailscale. The mandatory sanitiser runs before any network call and fails closed: if a row can't be confidently cleaned, it is dropped rather than risk sending it. The only payload that ever leaves is (row_index, cleaned_description, amount), and every outbound payload is written to a local audit log so it can be verified after the fact.

What leaves the machine

by design
  • Never: account numbers, BSBs, card numbers, balances, names, references, memos: regex-scrubbed, every digit run removed
  • Never: CSV inputs, the SQLite database, Excel files, or logs into git: blocked by pre-commit hooks
  • Only: anonymous merchant-and-amount pairs to OpenRouter for categorisation: a stream that could belong to anyone
  • Only: the monthly Excel workbook to your own Google Drive, via a service account you control
03 / ENGINEERING HIGHLIGHTS

F-01Content-based bank detection

Files are matched to a bank by their contents, not their upload slot: wrong-slot uploads still parse, unknown formats are rejected with a clear message.

F-02Idempotent pipeline

File and transaction fingerprinting make re-runs a no-op: no duplicate rows, no wasted categorisation calls, no changed output.

F-03Internal transfer netting

Money moved between own accounts is matched as opposite-sign pairs and excluded from totals, and from the categorisation payload entirely.

F-04Subscription watch

Recurring-merchant detection flags new subscriptions, price changes, and expected income that didn't arrive.

F-05Offline-tolerant phone PWA

Client-side queue-and-retry holds an upload made while the laptop is asleep and retries until it lands, privately, over Tailscale HTTPS.

F-06Always-on supervisor

A ~15 MB Windows service probes both servers every 15 s, relaunches whichever is down, and snapshots the database weekly via SQLite's online-backup API.

F-07Budget alerts, amount-free

Per-category budgets fire at 80% and 100%: alert payloads carry only a category name and a percent, never amounts or descriptions.

F-08Categoriser scorecard

A monthly accuracy card built from recategorisation events shows whether the LLM categoriser (swappable via config, with automatic fallback) improves over time.

04 / TRY IT
Self-hosted by design

The real thing is self-hosted: this is the demo

The actual app runs on your own machine, with your own keys, and the data never leaves your hardware. This demo is the same frontend, fed entirely by synthetic data: click every screen, flip the theme, run a search. Nothing here is real spending.

Open the live demo Source on GitHub
Scan to open the demo
05 / BUILT WITH
FastAPISQLite + FTS5Vite PWA Chart.jsOpenRouteropenpyxl Google Drive APIWeb Push / VAPIDTailscale Windows Task Scheduler